#include "Precompile.h"
#include "Mesh.h"

namespace Graphics{
	Mesh::Mesh(unsigned int modelId):	m_modelFile(""),m_modelId(modelId),Entity(Entity::ET_MESH),m_updateWorld(true),
										m_animationFPS(60),m_currentAnimationClip(-1),m_lastAnimationClip(-1),
										m_animationMarker(0.0f),m_animationTime(0.0f),m_timeWarp(1.0f),
										m_updateFrontVector(true),m_updateLeftVector(true),m_updateUpVector(true),
										m_animFadeing(false),m_fadeTime(DEFAULT_INTERPOLATE_TIME){
		m_orientation.vec(Utility::Math::_w_,1.0);
		m_orientation.normalize();
	}

	Mesh::Mesh(std::string& filename):	m_modelFile(filename),m_modelId(-1),Entity(Entity::ET_MESH),m_updateWorld(true),
										m_animationFPS(60),m_currentAnimationClip(-1),m_lastAnimationClip(-1),
										m_animationMarker(0.0f),m_animationTime(0.0f),m_timeWarp(1.0f),
										m_updateFrontVector(true),m_updateLeftVector(true),m_updateUpVector(true),
										m_animFadeing(false),m_fadeTime(DEFAULT_INTERPOLATE_TIME){
		m_orientation.vec(Utility::Math::_w_,1.0);
		m_orientation.normalize();
	}

	void Mesh::render(RenderContext* context, unsigned int inds, unsigned int indOffset){
		context->m_renderSystem->drawIndexed(inds,indOffset,0);
	}

	Utility::Math::Matrix<4,4> Mesh::getWorldMatrix(){
		if(m_updateWorld){
			Utility::Math::Matrix<4,4> translation,
											result,
											scale;
			result.createFromQuaternion(m_orientation);

			translation.set(3,0,m_position(Utility::Math::_x_));
			translation.set(3,1,m_position(Utility::Math::_y_));
			translation.set(3,2,m_position(Utility::Math::_z_));

			scale.set(0,0,m_scale);
			scale.set(1,1,m_scale);
			scale.set(2,2,m_scale);

			result *= translation;
			result *= scale;

			m_world = result;

			m_updateWorld = false;
		}
		return m_world;
	}

	void Mesh::rotate(Utility::Math::VectorPos axis, float amount){
		switch(axis){
		case Utility::Math::_x_:
			m_updateUpVector = true;
			m_updateLeftVector = true;
			break;
		case Utility::Math::_y_:
			m_updateFrontVector = true;
			m_updateLeftVector = true;
			break;
		case Utility::Math::_z_:
			m_updateFrontVector = true;
			m_updateUpVector = true;
			break;
		}
		Utility::Math::Quaternion<float> rotation;
		rotation.vec(axis,sinf(amount/2.0f));
		rotation.vec(Utility::Math::_w_,cosf(amount/2.0f));

		m_orientation = rotation * m_orientation;

		m_updateWorld = true;
	}

	void Mesh::move(Utility::Math::Vector<float> &dir){
		m_position += dir;

		m_updateWorld = true;
	}

	void Mesh::scale(float amount){
		m_scale += amount;

		m_updateWorld = true;
	}

	void Mesh::moveForward(float amount){
		if(m_updateFrontVector){
			Utility::Math::Quaternion<> qFront;
			qFront.vec(Utility::Math::_z_,1);
			qFront.vec(Utility::Math::_w_,0);

			qFront = m_orientation.conjugate()*qFront*m_orientation;
			m_front(Utility::Math::_x_,qFront.vec(Utility::Math::_x_));
			m_front(Utility::Math::_y_,qFront.vec(Utility::Math::_y_));
			m_front(Utility::Math::_z_,qFront.vec(Utility::Math::_z_));

			m_front.normalize();
			m_updateFrontVector = false;
		}

		move(m_front*amount);
	}

	void Mesh::moveUp(float amount){
		if(m_updateUpVector){
			Utility::Math::Quaternion<> qUp;
			qUp.vec(Utility::Math::_y_,1);
			qUp.vec(Utility::Math::_w_,0);

			qUp = m_orientation.conjugate()*qUp*m_orientation;
			m_up(Utility::Math::_x_,qUp.vec(Utility::Math::_x_));
			m_up(Utility::Math::_y_,qUp.vec(Utility::Math::_y_));
			m_up(Utility::Math::_z_,qUp.vec(Utility::Math::_z_));

			m_up.normalize();
			m_updateUpVector = false;
		}

		move(m_up*amount);
	}

	void Mesh::moveRight(float amount){
		if(m_updateLeftVector){
			Utility::Math::Quaternion<> qRight;
			qRight.vec(Utility::Math::_x_,1);
			qRight.vec(Utility::Math::_w_,0);

			qRight = m_orientation.conjugate()*qRight*m_orientation;
			m_right(Utility::Math::_x_,qRight.vec(Utility::Math::_x_));
			m_right(Utility::Math::_y_,qRight.vec(Utility::Math::_y_));
			m_right(Utility::Math::_z_,qRight.vec(Utility::Math::_z_));

			m_right.normalize();
			m_updateLeftVector = false;
		}

		move(m_right*amount);
	}

	void Mesh::playAnimation(std::string& name,bool loop,RenderContext* context, float transitionTime){
		ModelPtr model = context->m_renderSystem->getModel(m_modelId);
		if(!model)
			return;

		int controllerId = model->getControllerId();

		if(controllerId != -1){
			unsigned int clipId = context->m_animationManager->getAnimationController(controllerId)->getClipId(name);
			m_loopingAnimation = loop;

			if(clipId != m_currentAnimationClip){
				m_lastAnimationClip = m_currentAnimationClip;
				if(m_lastAnimationClip != -1 && transitionTime > 0){
					m_animFadeing = true;
					m_fadeTime = transitionTime;
					m_currentFadeTime = m_fadeTime;
					m_lastAnimationMarker = m_animationMarker;
				}
				m_currentAnimationClip = clipId;

				Graphics::AnimationClipPtr clipPtr = context->m_animationManager->getAnimationClip(m_currentAnimationClip);
				m_animationTime = m_animationMarker = clipPtr->getStartMarker();
			}
		}
		else{
			LOG_ERROR("Meshs model missing animation controller.");
			assert(0);
		}
	}

	void Mesh::stopAnimations(){
		m_currentAnimationClip = -1;
		m_lastAnimationClip = -1;
		m_loopingAnimation = false;
	}

	void Mesh::updateAnimation(float dt,RenderContext* context){
		ModelPtr model = context->m_renderSystem->getModel(m_modelId);
		if(!model)
			return;

		int controllerId = model->getControllerId();

		if(m_currentAnimationClip != -1 && controllerId != -1){
			if(m_animFadeing){
				m_currentFadeTime -= dt;
				if(m_currentFadeTime <= 0.0){
					m_currentFadeTime = 0.0;
					m_animFadeing = false;
				}
			}

			Graphics::AnimationClipPtr clipPtr = context->m_animationManager->getAnimationClip(m_currentAnimationClip);
			float start = clipPtr->getStartMarker(), end = clipPtr->getEndMarker();
			m_animationTime += dt*m_timeWarp;

			if(m_animationTime < start)
				m_animationTime = start;
			else if(m_animationTime > end){
				if(m_loopingAnimation)
					m_animationTime -= (end-start)*int((m_animationTime-start)/(end-start));
				else{
					m_animationTime = end;
					m_currentAnimationClip = -1;
				}
			}

			// Round to closest frame time
			float frameTime = 1.0f/float(m_animationFPS);
			int frame = int((m_animationTime-start)/frameTime);
			m_animationMarker = start+frame*frameTime;
		}
	}

	void Mesh::setWorld(Utility::Math::Matrix<4,4> world){
		m_world = world;
	}

	void Mesh::playAnimation_luaWrap(const char* name,bool loop,float transitionTime){
		playAnimation(std::string(name),loop,Graphics::GraphicsFactory::m_renderContext,transitionTime);
	}

	void Mesh::moveX_luaWrap(float amount){
		Utility::Math::Vector<> dir;
		dir(Utility::Math::_x_,amount);
		move(dir);
	}

	void Mesh::moveY_luaWrap(float amount){
		Utility::Math::Vector<> dir;
		dir(Utility::Math::_y_,amount);
		move(dir);
	}

	void Mesh::moveZ_luaWrap(float amount){
		Utility::Math::Vector<> dir;
		dir(Utility::Math::_z_,amount);
		move(dir);
	}

	void Mesh::rotateX_luaWrap(float amount){
		rotate(Utility::Math::_x_,amount);
	}

	void Mesh::rotateY_luaWrap(float amount){
		rotate(Utility::Math::_y_,amount);
	}

	void Mesh::rotateZ_luaWrap(float amount){
		rotate(Utility::Math::_z_,amount);
	}

	LuaPlus::LuaObject Mesh::getPosition_luaWrap(LuaPlus::LuaObject dummy){
		LuaPlus::LuaState* state = dummy.GetState();
		LuaPlus::LuaObject position;
		position.AssignNewTable(state);
		position.SetNumber("X",m_position(Utility::Math::_x_));
		position.SetNumber("Y",m_position(Utility::Math::_y_));
		position.SetNumber("Z",m_position(Utility::Math::_z_));

		return position;
	}
}